﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Web;
using System.Web.Caching;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;
using Netkk.Common;
using Netkk.Core.Auth;

namespace Netkk.Library
{
    public class Sitemap
    {
        private readonly XNamespace ns = "";
        private const string RootName = "siteMap";
        private const string NodeName = "siteMapNode";
        private const string MvcNodeName = "mvcSiteMapNode";

        private readonly string siteMapFile = string.Empty;
        private readonly string cacheKey = "122EF2B1-F0A4-4507-B011-94669840F79C";

        public Sitemap(string siteMapFile)
        {
            this.siteMapFile = HttpContext.Current.Server.MapPath(siteMapFile);
            cacheKey += HttpContext.Current.User.Identity.Name;
        }

        private XDocument SiteMapDocument
        {
            get
            {
                return CacheWrapper.Load(cacheKey + "xml", new CacheDependency(siteMapFile), () => XDocument.Load(siteMapFile));
            }
        }

        public SiteMapNode RootNode
        {
            get
            {
                return CacheWrapper.Load(cacheKey + "nodes", new CacheDependency(siteMapFile), () => LoadNodes());
            }
        }

        public SiteMapNode CurrentNode
        {
            get
            {
                HttpContext context = HttpContext.Current;
                SiteMapNode node = FindSiteMapNode(context);
                return IsAccessibleToUser(context, node) ? node : null;
            }
        }

        public static Sitemap Current()
        {
            Sitemap sitemap = null;
            if (HttpContext.Current.Items["currentsitemap"] != null)
            {
                sitemap = HttpContext.Current.Items["currentsitemap"] as Sitemap;
            }
            return sitemap;
        }

        public static Sitemap Create(string siteMapFile)
        {
            Sitemap sitemap = new Sitemap(siteMapFile);
            HttpContext.Current.Items.Add("currentsitemap", sitemap);
            return sitemap;
        }

        private SiteMapNode LoadNodes()
        {
            SiteMapNode rootNode;
            XDocument siteMapXml;

            try
            {
                // Load the XML document.
                siteMapXml = SiteMapDocument;

                // Get the rootNode siteMapNode element, and map this to a .NET SiteMapNode,
                // this becomes our rootNode node.
                XElement rootElement = siteMapXml.Element(ns + RootName).Element(ns + NodeName);
                rootNode = GetMvcSiteMapNodeFromXmlElement(rootElement);

                // Process our XML file, passing in the main rootNode sitemap node and xml element.
                ProcessXmlNodes(rootNode, rootElement);

            }
            catch (Exception ex)
            {
                throw new Exception("An error occured while parsing the sitemap XML.", ex);
            }
            finally
            {
                siteMapXml = null;
            }

            return rootNode;
        }

        protected SiteMapNode GetMvcSiteMapNodeFromXmlElement(XElement node)
        {
            string id = GetAttributeValue(node.Attribute("id"));

            SiteMapNode smNode = new SiteMapNode(id);

            IDictionary<string, object> routeValues = new Dictionary<string, object>();

            foreach (XAttribute attribute in node.Attributes())
            {
                string attributeName = attribute.Name.ToString();
                string attributeValue = attribute.Value;

                smNode[attributeName] = attributeValue;

                if (attributeName != "title" && attributeName != "description"
                    && attributeName != "resourceKey" && attributeName != "id"
                    && attributeName != "paramid" && attributeName != "role"
                    && attributeName != "visible")
                {
                    routeValues.Add(attributeName, attributeValue);
                }
                else if (attributeName == "paramid")
                {
                    routeValues.Add("id", attributeValue);
                    smNode.ParamId = attributeValue;
                }
                else if (attributeName == "role")
                {
                    smNode.Role = attributeValue.ToOrDefault<Roles>();
                }
                else if (attributeName == "visible")
                {
                    smNode.Visible = attributeValue.ToOrDefault(true);
                }
            }

            smNode.Title = smNode["title"];
            smNode.Description = smNode["description"];
            smNode.ResourceKey = smNode["resourceKey"];
            smNode.Controller = smNode["controller"];
            smNode.Action = smNode["action"] ?? "Index";

            if (!routeValues.ContainsKey("controller"))
                routeValues.Add("controller", "Home");
            if (!routeValues.ContainsKey("action"))
                routeValues.Add("action", "Index");


            HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
            if (routeData != null)
            {
                VirtualPathData virtualPath = routeData.Route.GetVirtualPath(new RequestContext(httpContext, routeData), new RouteValueDictionary(routeValues));
                if (virtualPath != null)
                {
                    smNode.Url = "~/" + virtualPath.VirtualPath;
                }
            }

            return smNode;
        }

        public string GetAttributeValue(XAttribute attribute)
        {
            return attribute != null ? attribute.Value : string.Empty;
        }

        protected void ProcessXmlNodes(SiteMapNode rootNode, XElement rootElement)
        {

            foreach (XElement node in rootElement.Elements())
            {
                SiteMapNode childNode;
                if (node.Name == ns + MvcNodeName)
                {
                    childNode = GetMvcSiteMapNodeFromXmlElement(node);

                    if (IsAccessibleToUser(HttpContext.Current, childNode))
                    {
                        rootNode.AddNode(childNode);
                    }
                }
                else
                {
                    throw new Exception("An invalid element was found in the sitemap.");
                }

                if (IsAccessibleToUser(HttpContext.Current, childNode))
                {
                    ProcessXmlNodes(childNode, node);
                }
            }
        }

        public SiteMapNode FindSiteMapNode(HttpContext context)
        {
            SiteMapNode node = null;

            HttpContextWrapper httpContext = new HttpContextWrapper(context);
            RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
            if (routeData != null)
            {
                IDictionary<string, object> routeValues = routeData.Values;
                string controller = (string)routeValues["controller"];
                string action = (string)routeValues["action"];
                string paramid = (string)routeValues["id"];

                node = FindControllerActionNode(RootNode, controller, action, paramid);
                if (node == null)
                {
                    node = FindControllerActionNode(RootNode, controller, action, String.Empty);
                }
                if (node == null)
                {
                    node = FindControllerActionNode(RootNode, controller, "Index", String.Empty);
                }
                if (node == null)
                {
                    node = FindControllerActionNode(RootNode, "Home", "Index", String.Empty);
                }
            }

            return node;
        }

        private SiteMapNode FindControllerActionNode(SiteMapNode currentNode, string controller, string action, string paramid)
        {
            if (currentNode != null)
            {
                foreach (SiteMapNode node in currentNode.ChildNodes)
                {
                    string nodeController = node["controller"] ?? "";
                    string nodeAction = node["action"] ?? "Index";
                    string nodeParamId = node["paramid"] ?? "";

                    if (nodeController.Equals(controller, StringComparison.OrdinalIgnoreCase)
                        && nodeAction.Equals(action, StringComparison.OrdinalIgnoreCase)
                        && nodeParamId.Equals(paramid, StringComparison.OrdinalIgnoreCase))
                    {
                        return node;
                    }

                    SiteMapNode siteMapNode = FindControllerActionNode(node, controller, action, paramid);
                    if (siteMapNode != null)
                    {
                        return siteMapNode;
                    }
                }
            }

            return null;
        }

        public bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
        {
            if (node == null) return false;

            // Find current handler
            MvcHandler handler = context.Handler as MvcHandler;

            if (handler != null)
            {

                // Find out current principal
                CustomPrincipal principal = new CustomPrincipal(handler.RequestContext.HttpContext.User.Identity);
                if (!principal.IsInRole(Roles.Admin))
                {
                    Roles nodeRole = node.Role;
                    if (nodeRole == Roles.Member && !principal.IsInRole(Roles.Member))
                    {
                        return false;
                    }
                }

                // It's an MvcSiteMapNode, try to figure out the controller class
                IController controller = ControllerBuilder.Current.GetControllerFactory().CreateController(handler.RequestContext, node.Controller);

                ArrayList actionAttributes = new ArrayList();
                MethodInfo[] methods = controller.GetType().GetMethods();
                foreach (MethodInfo method in methods)
                {
                    if (method.Name == node.Action)
                    {
                        actionAttributes.AddRange(method.GetCustomAttributes(typeof(IsAuthenticatedAttribute), true));
                        actionAttributes.AddRange(method.GetCustomAttributes(typeof(UserInRoleAttribute), true));
                        break;
                    }
                }


                foreach (object o in actionAttributes)
                {
                    if (!context.User.Identity.IsAuthenticated)
                    {
                        return false;
                    }
                    if (o is UserInRoleAttribute)
                    {
                        Roles role = ((UserInRoleAttribute)o).Role;
                        if (!principal.IsInRole(role))
                        {
                            return false;
                        }
                    }
                }


                return true;
            }

            return false;
        }




    }

    public class SiteMapNode
    {

        #region Properties

        public string Id { get; set; }

        public string Title { get; set; }
        public string Description { get; set; }
        public string ResourceKey { get; set; }
        public string Url { get; set; }
        public string Controller { get; set; }
        public string Action { get; set; }
        public string ParamId { get; set; }
        public Roles Role { get; set; }
        public bool Visible { get; set; }

        private List<SiteMapNode> childNodes;
        public List<SiteMapNode> ChildNodes
        {
            get
            {
                if (childNodes == null)
                {
                    childNodes = new List<SiteMapNode>();
                }
                return childNodes;
            }
            private set
            {
                childNodes = value;
            }
        }
        public SiteMapNode ParentNode { get; set; }

        public NameValueCollection Attributes { get; set; }

        #endregion

        public virtual string this[string key]
        {
            get
            {
                string defaultValue = null;
                if (Attributes != null)
                {
                    defaultValue = Attributes[key];
                }
                return defaultValue;
            }
            set
            {
                if (Attributes == null)
                {
                    Attributes = new NameValueCollection();
                }
                Attributes[key] = value;
            }
        }

        public SiteMapNode(string key)
        {
            Id = key;
            Visible = true;
        }

        public void AddNode(SiteMapNode node)
        {
            if (ChildNodes == null)
            {
                ChildNodes = new List<SiteMapNode>();
            }
            node.ParentNode = this;
            ChildNodes.Add(node);
        }

        public override bool Equals(object obj)
        {
            return Url == ((SiteMapNode)obj).Url;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return Url;
        }
    }

}
